﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Obra.Api;
using System.IO;
using Obra.Common;
using System.Xml;
using System.Xml.Serialization;

namespace Obra.SmugMug.Api
{
    /// <summary>
    /// Media file uploader for SmugMug.
    /// </summary>
    internal class UploadServiceProvider : 
        IUploadService,
        IDisposable
    {
        public const string DefaultUploadUrl = "http://upload.smugmug.com";

        public const string DefaultUploadHttpMethod = "PUT";

        private readonly WebClient webClient;

        private readonly string uploadUri;

        private readonly string sessionId;

        private readonly XmlSerializer serializer;

        /// <summary>
        /// c-tor
        /// </summary>
        public UploadServiceProvider(string sessionId)
        {
            this.webClient = new WebClient();
            this.uploadUri = UploadServiceProvider.DefaultUploadUrl;
            this.serializer = new XmlSerializer(typeof(UploadResponse));
            this.sessionId = sessionId;
        }

        /// <summary>
        /// c-tor
        /// </summary>
        /// <param name="uri">
        /// URI to upload media files to.
        /// </param>
        public UploadServiceProvider(
            string sessionId,
            string uploadUri)
            : this(sessionId)
        {
            this.uploadUri = uploadUri;
        }

        /// <summary>
        /// Insert the appropriate upload headers.
        /// </summary>
        /// <param name="client">
        /// Client to add the headers to.
        /// </param>
        /// <remarks>
        /// <para>
        /// SmugMug expects the following headers to be added to the request
        ///   + Content-Length (added by WebClient)
        ///   + Content-MD5
        ///   + X-Smug-SessionID
        ///   + X-Smug-Version
        ///   + X-Smug-ResponseType
        ///   + X-Smug-AlbumID
        ///   + X-Smug-ImageID (if you are overwriting an existing image)
        /// </para>
        /// </remarks>
        private void InsertHeaders(
            IAlbum album,
            string mediaName,
            Stream mediaStream)
        {
            this.webClient.Headers.Add(
                HttpRequestHeader.UserAgent, Obra.Api.Strings.UserAgent);
            this.webClient.Headers.Add(
                HttpRequestHeader.ContentMd5, new Checksum(mediaStream).ToString());

            this.webClient.Headers.Add("X-Smug-AlbumID", album.Id);
            this.webClient.Headers.Add("X-Smug-FileName", mediaName);
            this.webClient.Headers.Add("X-Smug-ResponseType", "REST");
            this.webClient.Headers.Add("X-Smug-SessionID", this.sessionId);
            this.webClient.Headers.Add("X-Smug-Version", Strings.ApiVersion);

            // If this item already exists overwrite the existing the 
            // version with new one.
            if (album.Contains(mediaName))
            {
                this.webClient.Headers.Add("X-Smug-ImageID", album[mediaName].Id);
            }

            // Rewind the stream.
            mediaStream.Seek(0, SeekOrigin.Begin);

            string message = String.Format("Uploading {0}/{1}\n{2}",
                album.Name,
                mediaName,
                this.webClient.Headers.ToString().Replace("\r\n\r\n", "\n").Replace("\r\n", "\n"));

            Log.Debug(
                message,
                LogCategory.SmugMug,
                LogCategory.Upload);
        }

        /// <summary>
        /// Deserialize the upload response.
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private UploadResponse Deserialize(byte[] bytes)
        {
            return (UploadResponse)this.serializer.Deserialize(
                new MemoryStream(bytes));
        }

        /// <summary>
        /// Check the response from SmugMug -- throw an exception if necessary.
        /// </summary>
        /// <param name="bytes">
        /// Response from SmugMug.
        /// </param>
        private void CheckResponse(byte[] bytes)
        {
            string result = Encoding.UTF8.GetString(bytes);

            Log.Debug(
                result,
                LogCategory.REST,
                LogCategory.XML,
                LogCategory.Upload);

            UploadResponse response = this.Deserialize(bytes);

            if (!response.IsSuccess())
            {
                throw new SmugMugException(response.Error.Message);
            }
        }

        /// <summary>
        /// Upload a media file to SmugMug.
        /// </summary>
        /// <param name="album">
        /// The album to send the media file to.
        /// </param>
        /// <param name="mediaName">
        /// The name of the media file.
        /// </param>
        /// <param name="mediaStream">
        /// Stream of the media file.
        /// </param>
        /// <remarks>
        /// <para>
        /// This is cheating because I am not using WCF.  I wish I could, but I
        /// am not what the correct solution is.
        /// </para>
        /// </remarks>
        public void UploadMediaFile(
            IAlbum album,
            string mediaName,
            Stream mediaStream)
        {
            this.webClient.Headers.Clear();

            this.InsertHeaders(
                album,
                mediaName,
                mediaStream);

            // Read the entire image
            // FIXME: stream the data, use HttpWebRequest
            byte[] buffer = new byte[mediaStream.Length];
            mediaStream.Read(buffer, 0, (int)mediaStream.Length);

            byte[] bytes = this.webClient.UploadData(
                new Uri(this.uploadUri + mediaName),
                UploadServiceProvider.DefaultUploadHttpMethod,
                buffer);

            this.CheckResponse(bytes);
        }

        #region IDisposable Members

        public void Dispose()
        {
            this.webClient.Dispose();
        }

        #endregion
    }
}
